package dal

import (
	"context"
	"fmt"
	"time"

	"code.byted.org/bytertc/whiteboard_server/biz/pkg/biz_error"
	"code.byted.org/gopkg/logs/v2/log"
	"github.com/pkg/errors"

	"code.byted.org/bytertc/whiteboard_server/biz/dal/spec"
	"code.byted.org/bytertc/whiteboard_server/biz/pkg/kv"
	"github.com/spf13/cast"
)

func genEnterKey(unique spec.OperatorUnique) string {
	return fmt.Sprintf("billEnter:%s:%s", unique.GetAppId(), unique.GetRoomId())
}

func genHbKey(unique spec.RoomUnique) string {
	return fmt.Sprintf("billHeartbeat:%s:%s", unique.GetAppId(), unique.GetRoomId())
}

func genRoomSetKey() string {
	return "billRooms"
}
func genRoomMemberKey(unique spec.RoomUnique) string {
	return fmt.Sprintf("%s%s%s", unique.GetAppId(), SplitCharacters, unique.GetRoomId())
}

func (r *repo) GetEnterTimeStamp(ctx context.Context, unique spec.OperatorUnique) (int64, error) {
	ret, err := r.cache.HGet(ctx, genEnterKey(unique), unique.GetOperator())
	if err != nil && !IsNotFoundError(err) {
		return 0, err
	}
	return cast.ToInt64(ret), nil

}
func (r *repo) GetEnterSetSize(ctx context.Context, unique spec.OperatorUnique) (int64, error) {
	ret, err := r.cache.HLen(ctx, genEnterKey(unique))
	return ret, err

}

func (r *repo) SetEnterTimeStamp(ctx context.Context, unique spec.OperatorUnique, timestamp int64) error {
	if timestamp <= 0 {
		return biz_error.InvalidParam.Wrap(errors.Errorf("invalid timestamp"))
	}
	_, err := r.cache.HSet(ctx, genEnterKey(unique), unique.GetOperator(), cast.ToString(timestamp))
	if err == nil {
		r.cache.Expire(ctx, genEnterKey(unique), time.Hour*24)
	}
	return err
}

func (r *repo) DelEnterTimeStamp(ctx context.Context, unique spec.OperatorUnique) error {
	_, err := r.cache.HDel(ctx, genEnterKey(unique), unique.GetOperator())
	return err

}

func (r *repo) DelHeartbeatTimeStamp(ctx context.Context, unique spec.OperatorUnique) error {
	err := r.cache.ZRem(ctx, genHbKey(unique), unique.GetOperator())
	return err
}

func (r *repo) UpdateHeartbeatTimeStamp(ctx context.Context, unique spec.OperatorUnique, timestamp int64) error {
	if timestamp <= 0 {
		return biz_error.InvalidParam.Wrap(errors.Errorf("invalid timestamp"))
	}
	member := kv.ZMember{
		Member: unique.GetOperator(),
		Score:  cast.ToFloat64(timestamp),
	}
	err := r.cache.ZAdd(ctx, genHbKey(unique), member)
	if err == nil {
		r.cache.Expire(ctx, genHbKey(unique), time.Hour*24)
	}
	return err

}
func (r *repo) SetFirstHbTimeStamp(ctx context.Context, unique spec.OperatorUnique, timestamp int64) error {
	if timestamp <= 0 {
		return biz_error.InvalidParam.Wrap(errors.Errorf("invalid timestamp"))
	}
	_, err := r.cache.HGet(ctx, genEnterKey(unique), unique.GetOperator())
	if err != nil && IsNotFoundError(err) {
		ok, err := r.cache.HSet(ctx, genEnterKey(unique), unique.GetOperator(), cast.ToString(timestamp))
		if err != nil {
			return biz_error.CacheError.Wrap(errors.WithStack(err))
		}
		if ok {
			r.cache.Expire(ctx, genEnterKey(unique), time.Hour*24)
			log.V1.CtxInfo(ctx, "SetFirstHbTimeStamp:%s,stamp:%d", genEnterKey(unique), timestamp)
		}
	}
	return nil
}

func (r *repo) GetHeartbeatTimeStamp(ctx context.Context, unique spec.OperatorUnique) (int64, error) {
	ret, err := r.cache.ZScore(ctx, genHbKey(unique), unique.GetOperator())
	if err != nil && !IsNotFoundError(err) {
		return 0, err
	}
	return cast.ToInt64(ret), nil
}
func (r *repo) GetHeartbeatTimeStamps(ctx context.Context, unique spec.RoomUnique, deadline int64) (map[string]int64, error) {
	zmembers, err := r.cache.ZRangeWithScore(ctx, genHbKey(unique), 0, 99)
	if err != nil {
		return nil, err
	}
	ret := make(map[string]int64)
	for _, zmember := range zmembers {
		score := cast.ToInt64(zmember.Score)
		if score > deadline {
			break
		}
		ret[cast.ToString(zmember.Member)] = score
	}
	return ret, nil
}

func (r *repo) UpdateRoomSet(ctx context.Context, unique spec.RoomUnique) error {
	member := kv.ZMember{
		Member: genRoomMemberKey(unique),
		Score:  cast.ToFloat64(time.Now().Unix()),
	}
	r.cache.ZAdd(ctx, genRoomSetKey(), member)
	return nil
}

func (r *repo) SetRoomSet(ctx context.Context, unique spec.RoomUnique) error {
	_, err := r.cache.ZScore(ctx, genRoomSetKey(), genRoomMemberKey(unique))
	if IsNotFoundError(err) {
		member := kv.ZMember{
			Member: genRoomMemberKey(unique),
			Score:  cast.ToFloat64(time.Now().Unix()),
		}
		r.cache.ZAdd(ctx, genRoomSetKey(), member)
	}
	return nil
}

func (r *repo) DelRoom(ctx context.Context, unique spec.RoomUnique) error {
	r.cache.ZRem(ctx, genRoomSetKey(), genRoomMemberKey(unique))
	return nil
}

func (r *repo) GetTopNRooms(ctx context.Context, top int64) ([]string, error) {
	zmembers, err := r.cache.ZRangeWithScore(ctx, genRoomSetKey(), 0, top-1)
	if err != nil {
		return nil, err
	}
	ret := make([]string, 0)
	for _, zmember := range zmembers {
		ret = append(ret, cast.ToString(zmember.Member))
	}
	return ret, nil

}
